<?php
/**
 * 分区数据记录
 *
 * @package if
 * @subpackage module.activerecord
 * @since 1.0
 */
abstract class IDbPartitionRecord extends IObject {
	protected $__type;
	protected $__action;
	protected $__deleted = false;
	protected $__saved = null;
	private $query;
	private $table;
	/**
	 * Enter description here...
	 *
	 * @var IDbPartitionDAO
	 */
	protected $dao;
	protected $__original = array();
	
	const HAS_ONE = "hasOne";
	const HAS_MANY = "hasMany";
	const BELONGS_TO = "belongsTo";
	const HAS_AND_BELONGS_TO_MANY = "hasAndBelongsToMany";
	
	/**
	 * 保存关联的对象
	 *
	 * @since 1.0
	 */
	const SAVE_RELATIONS = 1;
	
	/**
	 * 强制验证
	 *
	 * @since 1.0
	 */
	const SAVE_VALIDATE = 2;
	
	/**
	 * 强制检查脏数据
	 *
	 * @since 1.0
	 */
	const SAVE_DIRTY = 4; 
	
	/**
	 * 自动填充
	 *
	 * @since 1.0.3
	 */
	const SAVE_AUTO_FILL = 8;
	
	/**
	 * 一次性保存一批对象
	 *
	 * @since 1.0.3
	 */
	const SAVE_BATCH = 16;
	
	private $errors = array();
	
	private $hasErrors = false;
	
	static $_fill_time = true;
	
	private static $allErrors = array();

	/**
	 * 构造
	 *
	 * @param array $attrs 属性名值对
	 * @since 1.0
	 */
	public function __construct($table, array $attrs = null) {
		$this->__type = get_class($this);
		$this->dao = IDbPartitionDAO::dao($this->__type, $table);
		$this->table = $table;
		if ($attrs) {
			$this->setAttrs($attrs);
			$this->setOriginal($attrs);
		}
		$this->_init();
	}
	
	/**
	 * 初始化回调函数
	 * 
	 * 当对象被构造时，该方法将会被调用
	 *
	 * @since 1.0
	 */
	function _init() {}
	
	/**
	 * 新建记录之前的回调函数
	 *
	 * @since 1.0
	 */
	function _before_create() {}
	
	/**
	 * 新建记录之后的回调函数
	 *
	 * @since 1.0
	 */
	function _after_create() {}
	
	/**
	 * 新建记录和更新记录之前的回调函数
	 *
	 * @since 1.0
	 */
	function _before_create_and_update() {}
	
	/**
	 * 新建记录和更新记录之后的回调函数
	 *
	 * @since 1.0
	 */
	function _after_create_and_update() {}
	
	/**
	 * 读取记录之前的回调函数
	 *
	 * @since 1.0
	 */	
	function _before_read() {}
	
	/**
	 * 读取记录之后的回调函数
	 *
	 * @since 1.0
	 */		
	function _after_read() {}
	
	/**
	 * 更新记录之前的回调函数
	 *
	 * @since 1.0
	 */		
	function _before_update() {}
	
	/**
	 * 更新记录之后的回调函数
	 *
	 * @since 1.0
	 */		
	function _after_update() {}
	
	/**
	 * 删除记录之前的回调函数
	 *
	 * @since 1.0
	 */		
	function _before_delete() {}
	
	/**
	 * 删除记录之后的回调函数
	 *
	 * @since 1.0
	 */		
	function _after_delete() {}
	
	/**
	 * 校验记录之前的回调函数
	 *
	 * @since 1.0
	 */		
	function _before_validate() {}
	
	/**
	 * 检验记录之后的回调函数
	 *
	 * @since 1.0
	 */		
	function _after_validate() {}
	
	/**
	 * 取得当前的操作
	 * 
	 * 可能是create,read,update,delete中的一个
	 *
	 * @return string
	 * @since 1.0
	 */
	function action() {
		return $this->__action;
	}
	
	/**
	 * 设置当前的操作
	 *
	 * @param string $action 可能是create,read,update,delete中的一个
	 * @since 1.0
	 */
	function setAction($action) {
		$this->__action = $action;
	}
	
	/**
	 * 删除当前记录
	 * 
	 * 返回影响的行数
	 *
	 * @param boolean $deep 是否深度删除（删除与其关联的记录，目前未实现）
	 * @return integer
	 * @todo 实现$deep操作
	 * @since 1.0
	 */
	function delete($deep = false) {
		$this->__action = "delete";
		
		$this->dao->autofill($this, "delete");
		
		$this->_before_delete();
		
		//开始删除
		$count = $this->dao->delete($this, $deep);
		$this->__deleted = true;

		$this->_after_delete();
		
		return $count;
	}
	
	/**
	 * 判断当前记录是否已被删除
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function isDeleted() {
		return $this->__deleted;
	}
	
	/**
	 * 设置当前记录的删除状态
	 *
	 * @param boolean $deleted 是否被删除
	 * @since 1.0
	 */
	function setDeleted($deleted = true) {
		$this->__deleted = $deleted;
	}
	
	/**
	 * 将记录转换为数组
	 *
	 * @return array
	 * @since 1.0
	 */
	function asArray() {
		return $this->attrs(true);
	}
	
	/**
	 * 将记录转换为json
	 *
	 * @return string
	 * @since 1.0
	 */
	function asJSON() {
		$json = new IJSON($this->attrs());
		return $json->__toString();
	}
	
	/**
	 * 取得或设置某个属性的值
	 *
	 * @param string $name 属性名
	 * @param mixed $value 属性值，如果此值不为nil，则为设置属性值
	 * @param string|null $composite 所在的composite
	 * @return mixed
	 * @since 1.0
	 */
	function attr($name, $value = nil, $composite = null) {
		if (!if_is_nil($value)) {
			$this->setAttr($name, $value);
		}
		
		//从get中取
		$getter = "_get" . $name;
		if (method_exists($this, $getter)) {
			return $this->$getter();
		}
		$getter = "_get" . if_name_to_java($name);
		if (method_exists($this, $getter)) {
			return $this->$getter();
		}
		$vars = get_object_vars($this);
		if (isset($vars[$name])) {
			return $vars[$name];
		}
		
		//从composite中取
		$composites = $this->dao()->composite();
		if (isset($composites[$name])) {
			$config = $composites[$name];
			$value = null;
			if (is_array($config)) {
				$value = array();
				foreach ($config as $_vAttr => $_rAttr) {
					$value[$_vAttr] = $this->attr($_rAttr, nil, $name);
				}
			}
			else {
				$value = x($config);
				if (strstr($value, "$")) {
					$GLOBALS["tmp_record"] = $this;
					$value = preg_replace_callback("/\\$\{(\w+)}/", create_function('$match', 'return $GLOBALS["tmp_record"]->attr($match[1]);'), $value);
					unset($GLOBALS["tmp_record"]);
				}
			}
			return $value;
		}
		
		//展开composite
		foreach ($composites as $cAttr => $def) {
			if ($composite == $cAttr) {
				continue;
			}
			if (is_array($def) && in_array($name, $def)) {
				$compositeValue = $this->attr($cAttr);
				if (is_array($compositeValue)) {
					$keys = array_keys($def, $name);
					return isset($compositeValue[$keys[0]]) ? $compositeValue[$keys[0]] : null;
				}
				else {
					return null;
				}
			}
		}
		
		return null;
	}
	
	/**
	 * 取得所有属性的值
	 *
	 * @param boolean $all 是否包括外键关联的属性
	 * @return array
	 * @since 1.0
	 */
	function attrs($all = true) {
		$attrs = array();
		foreach ($this->dao->columns() as $column) {
			$attrs[$column] = $this->attr($column);
		}
		if ($all) {
			foreach ($this->dao->relations() as $relation) {
				$attrs[$relation["attr"]] = $this->attr($relation["attr"]);
			}
		}
		return $attrs;
	}
	
	/**
	 * 设置属性的值
	 *
	 * @param string $name 属性名
	 * @param mixed $value 属性值
	 * @since 1.0
	 */
	function setAttr($name, $value) {
		if (!is_string($name)) {
			return;
		}
		$setter = "_set" . $name;
		if (method_exists($this, $setter)) {
			$this->$setter($value);
			return;
		}
		$this->$name = $value;
	}
	
	/**
	 * 设置一批属性的值
	 * 
	 * 在1.0.3中加入$except,$only参数，用来限制当前对象能接收的属性
	 * - $record->setAttrs(x(), "id,name")
	 * - $record->setAttrs(x(), "name,age,height,weight");
	 *
	 * @param array $attrs 属性名值对
	 * @param array|string $except 需要剔除的属性列表，在1.0.3中加入
	 * @param array|string $only 限制的属性列表，在1.0.3中加入
	 * @since 1.0
	 */
	function setAttrs(array $attrs, $except = "", $only = "") {
		foreach ($attrs as $name=>$value) {
			if (!$except || !if_in_array($name, $except)) {
				if (!$only || if_in_array($name, $only)) {
					$this->setAttr($name, $value);
				}
			}
		}
	}
	
	/**
	 * 当前记录的数据访问对象
	 *
	 * @return IDbPartitionDAO
	 * @since 1.0
	 */
	function dao() {
		return $this->dao;
	}
	
	/**
	 * 判断当前记录是脏数据（是否被修改过）
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function isDirty() {
		return $this->attrs(false) != $this->original();
	}
	
	/**
	 * 判断记录是否已被保存
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function isSaved() {
		if (!is_null($this->__saved)) {
			return $this->__saved;
		}
		$pk = $this->pk();
		return !is_null($pk);
	}
	
	/**
	 * 设置记录的保存状态
	 *
	 * @param boolean $saved 是否已被保存
	 * @since 1.0
	 */
	function setSaved($saved = true) {
		$this->__saved = $saved;
	}
	
	/**
	 * 设置当前记录的类型
	 *
	 * @param string $type 记录类型，一般为类名称
	 * @since 1.0
	 */
	function setType($type) {
		$this->__type = $type;
	}
	
	/**
	 * 取得当前记录的主键值
	 *
	 * @return mixed
	 * @since 1.0
	 */
	function pk() {
		return $this->attr($this->dao->pkName());
	}
	
	/**
	 * 保存记录
	 * 
	 * 从1.0.3开始，增加了IDbPartitionRecord::SAVE_BATCH参数，可以让该函数一次性添加多条记录，并返回一个记录集：
	 * <code>
	 * $log = new TLog();
	 * $log->save(array(
	 * 	"title" => "Log Title",
	 * 	"category_id" => array(1, 2, 3, 4) //注意是数组
	 * ), IDbPartitionRecord::SAVE_BATCH);
	 * </code>
	 * 然后执行时，I-F会自动将字段值为数组的category_id展开，执行四次保存，相当于：
	 * <code>
	 * foreach ($categoryIds as $categoryId) {
	 * 	$log = new TLog();
	 * 	$log->save(array(
	 * 		"title" => "Log Title",
	 * 		"category_id" => $categoryId
	 * 	));
	 * }
	 * </code>
	 * 
	 * 在使用SAVE_BATCH时，如果有多个字段的值都是数组，那么将生成字段值总数的乘积的结果，比如a字段有n1个，b字段有n2个，那么最终结果将生成 n1 * n2 条记录。
	 * 
	 *
	 * @param array $attrs 属性的名值对
	 * @param integer $opt1 选项，请查看IDbPartitionRecord::SAVE_*常量，默认为SAVE_VALIDATE,SAVE_RELATIONS,SAVE_AUTO_FILL
	 * @param integer $opt2 选项，请查看IDbPartitionRecord::SAVE_*常量，默认为SAVE_VALIDATE,SAVE_RELATIONS,SAVE_AUTO_FILL
	 * @return IDbPartitionRecord|IDbPartitionRecordSet 从v1.0.3加入
	 * @since 1.0
	 */
	function save(array $attrs = null, $opt1 = null) {	
		//参数
		$_opts = self::SAVE_DIRTY | self::SAVE_VALIDATE | self::SAVE_AUTO_FILL;
		$arguments = func_get_args();
		unset($arguments[0]);
		foreach ($arguments as $argument) {
			if (is_int($argument)) {
				$_opts = ($argument > 0) ? $_opts | $argument : $_opts & $argument;
			}
		}
		
		//设置属性
		if (is_array($attrs)) {
			$this->setAttrs($attrs);
		}
		
		//是否为批量添加
		if ($_opts & self::SAVE_BATCH) {
			$recordSet = new IDbPartitionRecordSet($this->dao);
			$found = false;
			foreach ($this->dao->columns() as $column) {
				$columnValue = $this->attr($column);
				if (is_array($columnValue)) {
					if (!empty($columnValue)) {
						$found = true;
						foreach ($columnValue as $value) {
							$record = clone $this;
							$record->setAttr($column, $value);
							$params = $arguments;
							array_unshift($params, array());
							$set = call_user_func_array(array($record, "save"), $params);
							$recordSet->addAll($set->records());
						}
						break;
					}
					else {
						$this->setAttr($column, null);
					}
				}
			}
			if (!$found) {
				$params = $arguments;
				array_unshift($params, array());
				$params[] = ~IDbPartitionRecord::SAVE_BATCH;
				call_user_func_array(array($this, "save"), $params);
				$recordSet->add($this);
			}
			return $recordSet;
		}
		
		//避免死循环所做的追踪
		$_traces = debug_backtrace();
		$trace = array();
		foreach ($_traces as $value) {
			if (isset($value["function"]) && isset($value["object"])) {
				$trace[strtolower($value["function"])] = $value["object"];
			}
		}
		
		$this->__action = $this->isSaved()? "update" : "create";
		
		if ($this->isSaved()) {//update
			if ($_opts & self::SAVE_AUTO_FILL) {
				$this->dao->autofill($this, "update");
			}
			if (!$this->_isRecall($trace, "_before_create_and_update")) {
				$this->_before_create_and_update();
			}
			if (!$this->_isRecall($trace, "_before_update")) {
				$this->_before_update();
			}
			if ($_opts & self::SAVE_VALIDATE) {
				$this->validate();
			}
			$this->dao->save($this, !(self::SAVE_DIRTY & $_opts));
			$this->setOriginal(get_object_vars($this));
			if (!$this->_isRecall($trace, "_after_create_and_update")) {
				$this->_after_create_and_update();
			}
			if (!$this->_isRecall($trace, "_after_update")) {
				$this->_after_update();
			}
		}
		else {//create
			if ($_opts & self::SAVE_AUTO_FILL) {
				$this->dao->autofill($this, "create");
			}
			if (!$this->_isRecall($trace, "_before_create_and_update")) {
				$this->_before_create_and_update();
			}
			if (!$this->_isRecall($trace, "_before_create")) {
				$this->_before_create();
			}
			if ($_opts & self::SAVE_VALIDATE) {
				$this->validate();
			}
			$this->dao->save($this);
			$this->setOriginal(get_object_vars($this));
			if (!$this->_isRecall($trace, "_after_create_and_update")) {
				$this->_after_create_and_update();
			}
			if (!$this->_isRecall($trace, "_after_create")) {
				$this->_after_create();
			}
		}
		if ($_opts & self::SAVE_RELATIONS) {
			$this->saveRelations();
		}
		return $this;
	}
	
	private function _isRecall($trace, $callback) {
		return isset($trace[$callback]) && $trace[$callback] === $this;
	}
	
	/**
	 * 保存关联的对象值
	 * 
	 * @since 1.0
	 */
	function saveRelations() {
		$relations = $this->dao()->relations();
		foreach ($relations as $relation) {
			extract($relation);
			$value = $this->attr($attr);
			$targetDAO = IDbPartitionDAO::dao($target, $this->table);	
			$targetPk = $targetDAO->pkName();
			$fkValue = $this->attr($fk);
			
			//去除关系
			if (if_is_nil($value)) {
				if ($type == self::HAS_ONE || $type == self::HAS_MANY) {
					IDbPartitionDAO::query($target)->attr($by, $fkValue)->update(array(
						$by => 0
					));
				}
				else if ($type == self::BELONGS_TO) {
					$this->save(array(
						$fk => 0
					));
				}
				else if ($type == self::HAS_AND_BELONGS_TO_MANY) {
					IDbPartitionDAO::query($joinTable)->attr($joinFk, $fkValue)->delete();
				}
				
				//修改值
				$this->setAttr($attr, null);
				continue;
			}
			else if (!is_object($value) && !is_array($value)) {
				continue;
			}
			else {
				if ($type == self::HAS_ONE) {
					$value = $this->_record2Array($attr, $value);
					$value[$by] = $fkValue;
					
					//处理原有的
					IDbPartitionDAO::query($target)->attr($by, $fkValue)->update(array(
						$by => 0
					));
					
					if (!isset($value[$targetPk])) {//create new
						$record = $targetDAO->create($value);
						$record->save($value);
					}
					else {
						$record = q($target)->find($value[$targetPk]);
						if ($record) {
							$record->save($value);
						}
					}
				}
				else if ($type == self::HAS_MANY) {
					//处理原有的
					IDbPartitionDAO::query($target)->attr($by, $fkValue)->update(array(
						$by => 0
					));
					if (is_array($value)) {
						$set = new IDbPartitionRecordSet($targetDAO);
						$set->setRecords($value);
						$value = $set;
					}
					if (if_is_instance_of($value, "IDbPartitionRecordSet")) {
						foreach ($value as $record) {
							$pkValue = $record->attr($targetPk);
							$record->setAttr($by, $fkValue);
							$targetDAO->save($record, true);
						}
					}
				}
				else if ($type == self::BELONGS_TO) {
					//处理原有的
					$value = $this->_record2Array($attr, $value);
					$record = $targetDAO->create($value);
					$targetDAO->save($record, true);
					$this->save(array(
						$fk => $record->attr($by)
					));
				}
				else if ($type == self::HAS_AND_BELONGS_TO_MANY) {
					if (is_array($value)) {
						$set = new IDbPartitionRecordSet($targetDAO);
						$set->setRecords($value);
						$value = $set;
					}
					if (if_is_instance_of($value, "IDbPartitionRecordSet")) {
						$bys = array();
						$quotedBys = array();
						foreach ($value as $record) {
							$targetDAO->save($record, true);
							$quotedBys[] = $this->dao->driver()->quote($record->attr($by));
							$bys[] = $record->attr($by);
						}
						if (empty($bys)) {
							IDbPartitionDAO::query($joinTable)->attr($joinFk, $fkValue)->delete();
						}
						else {
							q($joinTable)->attr($joinFk, $fkValue)->cond("self.{$joinBy} NOT IN (" . implode(",", $bys) . ")")->delete();
							
							//已有的bys
							$oldBys = if_array_pick(q($joinTable)->attr($joinFk, $fkValue)->findAll(), $joinBy);
							$newBys = array_diff($bys, $oldBys);
							foreach ($newBys as $by) {
								IDbPartitionDAO::dao($joinTable, $this->table)->create(array(
									$joinFk => $fkValue,
									$joinBy => $by
								))->save();
							}
						}
					}
				}
			}
 		}
	}
	
	private function _record2Array($attr, $record) {
		if (if_is_instance_of($record, "IDbPartitionRecord")) {
			$record = $record->attrs();
		}
		if (!is_array($record)) {
			throw new IActiveRecordException("invalid value for " . $this->__type . "::" . $attr, 0x100062);
		}
		return $record;
	}
	
	private function _removeNull($array) {
		foreach ($array as $key => $value) {
			if (is_null($value)) {
				unset($array[$key]);
			}
		}
		return $array;
	}
	
	/**
	 * 取得记录的类型
	 *
	 * @return string
	 * @since 1.0
	 */
	function type() {
		return $this->__type;
	}
	
	/**
	 * 魔术方法
	 *
	 * @param string $name 属性名
	 * @return mixed
	 * @since 1.0
	 */
	function __get($name) {
		$obj = $this->attr($name);
		return $obj;
	}
	
	/**
	 * 魔术方法
	 *
	 * @param string $name 属性名
	 * @param mixed $value 值
	 * @since 1.0
	 */
	function __set($name, $value) {
		return $this->setAttr($name, $value);
	}
	
	/**
	 * 魔术方法
	 *
	 * @return array
	 * @since 1.0
	 */
	function __sleep() {
		$vars = array_keys(get_object_vars($this));
		return array_diff($vars, array("dao", "errors", "hasErrors", "query"));
	}
	
	/**
	 * 魔术方法
	 *
	 * @since 1.0
	 */
	function __wakeup() {
		$this->__construct();
	}
	
	/**
	 * 设置记录的原始属性值
	 *
	 * @param array $original 原始属性值
	 * @since 1.0
	 */
	function setOriginal(array $original) {
		$this->__original = $original;
		/**foreach ($this->dao->columns() as $column) {
			if (isset($original[$column])) {
				$this->__original[$column] = $original[$column];
			}
			else {
				$this->__original[$column] = null;
			}
		}**/
	}
	
	/**
	 * 取得记录的原始属性值
	 *
	 * @return array
	 * @since 1.0
	 */
	function original() {
		return $this->__original;
	}
	
	/**
	 * 判断一个偏移量是否存在
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index
	 * @return boolean
	 * @since 1.0
	 */
	function offsetExists($index) {
		return !is_null($this->attr($index));
	}
	
	/**
	 * 从一个偏移量中取得数据
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 * @return mixed
	 * @since 1.0
	 */
	function offsetGet($index) {
		return $this->attr($index);
	}
	
	/**
	 * 设置偏移量位置上的值
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index 偏移量
	 * @param mixed $item 值
	 * @since 1.0
	 */
	function offsetSet($index, $item) {
		$this->setAttr($index, $item);
	}
	
	/**
	 * 删除偏移量位置对应的元素
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 * @since 1.0
	 */
	function offsetUnset($index) {
		$this->setAttr($index, null);
	}		
	
	/**
	 * 判断当前记录是否有某个特定的属性
	 *
	 * @param string $attr 属性名
	 * @return boolean
	 * @since 1.0
	 */
	function has($attr) {
		return isset($this[$attr]);
	}
	
	/**
	 * 取得错误信息
	 * 
	 * 如果指定了属性，则只返回该属性对应的错误信息
	 *
	 * @param string $attr 属性
	 * @return array
	 * @since 1.0
	 */
	function errors($attr = null) {
		if ($attr) {
			return isset($this->errors[$attr])?$this->errors[$attr]:array();
		}
		return $this->errors;
	}
	
	/**
	 * 当前记录是否有错误
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function hasErrors() {
		return $this->hasErrors;
	}
	
	/**
	 * 添加一个或一组属性错误信息
	 *
	 * @param string $attrOrErrors 属性或错误列表
	 * @param string|array $error 错误信息
	 * @since 1.0
	 */
	function addError($attr, $error) {
		if (!is_array($error)) {
			$error = array($error);
		}
		if (!isset($this->errors[$attr])) {
			$this->errors[$attr] = array();
		}
		$this->errors[$attr]= if_array_join($this->errors[$attr], $error);
		
		//all errors
		if (!isset(self::$allErrors[$this->__type])) {
			self::$allErrors[$this->__type] = array();
		}
		if (!isset(self::$allErrors[$this->__type][$attr])) {
			self::$allErrors[$this->__type][$attr] = array();
		}
		self::$allErrors[$this->__type][$attr] = if_array_join(self::$allErrors[$this->__type][$attr], $error);
	}
	
	/**
	 * 校验当前记录
	 * 
	 * 可以在校验器中使用 
	 * - <string> on
	 * - <boolean> skipOnEmpty
	 * - <boolean> breakOnError
	 * 
	 * @since 1.0
	 */
	function validate() {
		$this->_before_validate();
		if (IApplication::hasModule("validator")) {
			foreach ($this->dao->validates() as $attr=>$validators) {
				$oldValue = $attrValue = $this->attr($attr);
				$opts = array();
				//支持选项
				if (array_key_exists("opts", $validators)) {
					if (is_array($validators["opts"])) {
						$opts = $validators["opts"];
					}
					unset($validators["opts"]);
				}
				//开始验证
				$validator = new IValidator($attr, $attrValue, $opts);
				foreach ($validators as $ruleName => $opts) {
					if (!is_array($opts)) {
						$opts = array($opts);
					}
					//对 on=>"create" 的处理
					if (!isset($opts["on"]) || $opts["on"] == $this->__action) {
						if (is_string($ruleName)) {
							array_unshift($opts, $ruleName);
						}

						if (is_string($opts[0]) && strstr($opts[0], ".")) {//self.validate
							$opts[0] = explode(".", $opts[0]);
						}
						
						//对skipOnEmpty的处理
						if (!in_array($opts[0], array("required", "notEmpty")) && (!isset($opts["skipOnEmpty"]) || $opts["skipOnEmpty"]) && empty($attrValue)) {
							continue;		
						}
						
						//处理自定义规则
						if (isset($opts[0]) && is_array($opts[0])) {
							$callback = $opts[0];
							if (isset($callback[0]) && $callback[0] == "self") {//array("self", "validate")
								$callback[0] = $this;
							}
							$opts[0] = $callback;
						}
						$result = $validator->applyOpts($opts);
						
						//对breakOnError的处理
						if ((!isset($opts["breakOnError"]) || $opts["breakOnError"]) && !$result) {
							break;		
						}
					}
				}
				$errors = $validator->errors();
				if (!empty($errors)) {
					$this->hasErrors = true;
					$this->addError($attr, $errors);
				}
				
				//处理value
				if ($oldValue !== $attrValue) {
					$this->setAttr($attr, $attrValue);
				}
			}
			if ($this->hasErrors) {
				throw new IValidateException("record {$this->__type} validate failure", 0x100063);
			}
		}
		$this->_after_validate();
	}
	
	/**
	 * 构造关联表查询
	 *
	 * @param string|null $relationAttr 关联的名称，即关联表在当前对象的属性名
	 * @return IDbPartitionQuery
	 * @since 1.0
	 */
	function query($relationAttr = null) {
		if (is_null($relationAttr)) {
			return q($this->__type)->pk($this->pk());
		}
		
		//是不是关联表
		$relation = $this->dao->relation($relationAttr);
		if (is_array($relation)) {
			$fkValue = $this->attr($relation["fk"]);
			$query = q($relation["target"]);
			if ($relation["type"] != self::HAS_AND_BELONGS_TO_MANY) {
				$query->attr($relation["by"], $fkValue);
			}
			else {
				//target和joinTable是否有关联
				$found = false;
				$targetDAO = IDbPartitionDAO::dao($relation["target"], $this->table);
				foreach ($targetDAO->relations() as $_relation) {
					if ($_relation["target"] == $relation["joinTable"]) {
						$found = true;
						$query->attr($_relation["attr"] . "." . $relation["joinFk"], $fkValue);
					}
				}
				if (!$found) {
					$joinAttr = $relation["joinTable"];
					$targetDAO->addRelation(array(
						"type" => self::HAS_MANY,
						"attr" => $joinAttr,
						"fk" => $relation["by"],
						"target" => $relation["joinTable"],
						"by" => $relation["joinBy"]
					));
					$query->attr($joinAttr . "." . $relation["joinFk"], $fkValue);
				}
			}
			return $query;
		}
		else if (substr($relationAttr, 0, 1) == "&"){
			$attr = substr($relationAttr, 1);
			$relation = $this->dao->relation($attr);
			if (!empty($relation) && $relation["type"] == self::HAS_AND_BELONGS_TO_MANY) {
				$query = q($relation["joinTable"])->attr($relation["joinFk"], $this->attr($relation["fk"]));
				return $query;
			}
		}
		
		throw new IActiveRecordException("no relation for '{$relationAttr}' on '{$this->__type}'", 0x100064);
	}
	
	/**
	 * 过滤当前模型对象的每一个属性
	 *
	 * @param string|array $filterFunction 过滤函数，接受: 当前对象，属性名，属性值三个字段
	 * @since 1.0
	 */
	function filter($filterFunction) {
		if (!is_callable($filterFunction)) {
			throw new IException("The record filter is not callable.", 0x100015);
		}
		foreach ($this->attrs() as $name => $value) {
			$ret = call_user_func_array($filterFunction, array(&$this, $name, &$value));
			$this->setAttr($name, (is_bool($ret) && !$ret) ? null : $value);
		}
	}
	
	/**
	 * 取得所有模型的错误信息
	 *
	 * @return array
	 * @since 1.0
	 */
	static function allErrors() {
		return self::$allErrors;
	}
	
	/**
	 * 设置关联的查询
	 *
	 * @param IDbPartitionQuery $query 查询
	 * @since 1.0.2
	 */
	function setQuery($query) {
		$this->query = $query;
	}
	
	/**
	 * 刷新当前记录
	 *
	 * @return IDbPartitionRecord
	 * @since 1.0.2
	 */
	function reload() {
		if ($this->query) {
			$this->setAttrs($this->query->find(nil, true));
		}
		return $this;
	}
}

?>